#-
# ==========================================================================
# Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors.  All 
# rights reserved.
#
# The coded instructions, statements, computer programs, and/or related 
# material (collectively the "Data") in these files contain unpublished 
# information proprietary to Autodesk, Inc. ("Autodesk") and/or its 
# licensors, which is protected by U.S. and Canadian federal copyright 
# law and by international treaties.
#
# The Data is provided for use exclusively by You. You have the right 
# to use, modify, and incorporate this Data into other products for 
# purposes authorized by the Autodesk software license agreement, 
# without fee.
#
# The copyright notices in the Software and this entire statement, 
# including the above license grant, this restriction and the 
# following disclaimer, must be included in all copies of the 
# Software, in whole or in part, and all derivative works of 
# the Software, unless such copies or derivative works are solely 
# in the form of machine-executable object code generated by a 
# source language processor.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 
# AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED 
# WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF 
# NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
# PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR 
# TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS 
# BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, 
# DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK 
# AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY 
# OR PROBABILITY OF SUCH DAMAGES.
#
# ==========================================================================
#+

#
#	Creation Date:   2 October 2006
#
#	Description:
# 
#		moveTool.py
#
# Description:
#	Interactive tool for moving objects and components.
#
#	This plug-in will register the following two commands in Maya:
#		maya.cmds.spMoveToolCmd(x, y, z)
#       maya.cmds.spMoveToolContext()
#
#	Usage:
#	import maya
#	maya.cmds.loadPlugin("moveTool.py")
#	maya.cmds.spMoveToolContext("spMoveToolContext1")
#	shelfTopLevel = maya.mel.eval("global string $gShelfTopLevel;$temp = $gShelfTopLevel")
#	maya.cmds.setParent("%s|General" % shelfTopLevel)
#	maya.cmds.toolButton("spMoveTool1", cl="toolCluster", t="spMoveToolContext1", i1="moveTool.xpm") 
#
#	Remove UI objects with
#	maya.cmds.deleteUI("spMoveToolContext1")
#	maya.cmds.deleteUI("spMoveTool1")
#

import maya.OpenMaya as OpenMaya
import maya.OpenMayaMPx as OpenMayaMPx
import maya.OpenMayaUI as OpenMayaUI
import sys, math

kPluginCmdName="spMoveToolCmd"
kPluginCtxName="spMoveToolContext"
kVectorEpsilon = 1.0e-3

# keep track of instances of MoveToolCmd to get around script limitation
# with proxy classes of base pointers that actually point to derived
# classes
kTrackingDictionary = {}

# command
class MoveToolCmd(OpenMayaMPx.MPxToolCommand):
	kDoIt, kUndoIt, kRedoIt = 0, 1, 2

	def __init__(self):
		OpenMayaMPx.MPxToolCommand.__init__(self)
		self.setCommandString(kPluginCmdName)
		self.__delta = OpenMaya.MVector()
		kTrackingDictionary[OpenMayaMPx.asHashable(self)] = self

	def __del__(self):
		del kTrackingDictionary[OpenMayaMPx.asHashable(self)]

	def doIt(self, args):
		argData = OpenMaya.MArgDatabase(self.syntax(), args)
		vector = OpenMaya.MVector(1.0, 0.0, 0.0)
		if args.length() == 1:
			vector.x = args.asDouble(0)
		elif args.length == 2:
			vector.x = args.asDouble(0)
			vector.y = args.asDouble(1)
		elif args.length == 3:
			vector.x = args.asDouble(0)
			vector.y = args.asDouble(1)
			vector.y = args.asDouble(2)
		self.__action(MoveToolCmd.kDoIt)

	def redoIt(self):
		self.__action(MoveToolCmd.kRedoIt)

	def undoIt(self):
		self.__action(MoveToolCmd.kUndoIt)

	def isUndoable(self):
		return True

	def finalize(self):
		"""
		Command is finished, construct a string for the command
		for journalling.
		"""
		command = OpenMaya.MArgList()
		command.addArg(self.commandString())
		command.addArg(self.__delta.x)
		command.addArg(self.__delta.y)
		command.addArg(self.__delta.z)

		# This call adds the command to the undo queue and sets
		# the journal string for the command.
		#
		try:
			OpenMayaMPx.MPxToolCommand._doFinalize(self, command)
		except:
			pass

	def setVector(self, x, y, z):
		self.__delta.x = x
		self.__delta.y = y
		self.__delta.z = z

	def __action(self, flag):
		"""
		Do the actual work here to move the objects	by vector
		"""
		vector = self.__delta
		if flag == MoveToolCmd.kUndoIt:
			vector.x = -vector.x
			vector.y = -vector.y
			vector.z = -vector.z
		else:
			# all other cases identical
			pass

		# Create a selection list iterator
		#
		slist = OpenMaya.MSelectionList()
		OpenMaya.MGlobal.getActiveSelectionList(slist)
		sIter = OpenMaya.MItSelectionList(slist)

		mdagPath = OpenMaya.MDagPath()
		mComponent = OpenMaya.MObject()
		spc = OpenMaya.MSpace.kWorld

		# Translate all selected objects
		#
		while not sIter.isDone():
			# Get path and possibly a component
			#
			sIter.getDagPath(mdagPath, mComponent)
			try:
				transFn = OpenMaya.MFnTransform(mdagPath)
			except:
				pass
			else:
				try:
					transFn.translateBy(vector, spc)
				except:
					sys.stderr.write("Error doing translate on transform\n")
				sIter.next()
				continue

			try:
				cvFn = OpenMaya.MItCurveCV(mdagPath, mComponent)
			except:
				pass
			else:
				while not cvFn.isDone():
					cvFn.translateBy(vector, spc)
					cvFn.next()
				cvFn.updateCurve()

			try:
				sCvFn = OpenMaya.MItSurfaceCV(mdagPath, mComponent, True)
			except:
				pass

			else:
				while not sCvFn.isDone():
					while not CvFn.isRowDone():
						sCvFn.translateBy(vector, spc)
						sCvFn.next()
					sCvFn.nextRow()
				sCvFn.updateSurface()

			try:
				vtxFn = OpenMaya.MItMeshVertex(mdagPath, mComponent)
			except:
				pass
			else:
				while not vtxFn.isDone():
					vtxFn.translateBy(vector, spc)
					vtxFn.next()
				vtxFn.updateSurface()

			sIter.next()


class MoveContext(OpenMayaMPx.MPxSelectionContext):
	kTop, kFront, kSide, kPersp = 0, 1, 2, 3
	
	def __init__(self):
		OpenMayaMPx.MPxSelectionContext.__init__(self)
		self._setTitleString("moveTool")
		self.setImage("moveTool.xpm", OpenMayaMPx.MPxContext.kImage1)
		self.__currWin = 0
		self.__view = OpenMayaUI.M3dView()
		self.__startPos_x = 0
		self.__endPos_x = 0
		self.__startPos_y = 0
		self.__endPos_y = 0
		self.__cmd = None

	def toolOnSetup(self, event):
		self._setHelpString("drag to move selected object")

	def doPress(self, event):
		OpenMayaMPx.MPxSelectionContext.doPress(self, event)
		spc = OpenMaya.MSpace.kWorld
		
		# If we are not in selecting mode (i.e. an object has been selected)
		# then set up for the translation.
		#
		if not self._isSelecting():
			argX = OpenMaya.MScriptUtil()
			argX.createFromInt(0)
			argXPtr = argX.asShortPtr()
			argY = OpenMaya.MScriptUtil()
			argY.createFromInt(0)
			argYPtr = argY.asShortPtr()
			event.getPosition(argXPtr, argYPtr)
			self.__startPos_x = OpenMaya.MScriptUtil(argXPtr).asShort()
			self.__startPos_y = OpenMaya.MScriptUtil(argYPtr).asShort()
			self.__view = OpenMayaUI.M3dView.active3dView()

			camera = OpenMaya.MDagPath()
			self.__view.getCamera(camera)
			fnCamera = OpenMaya.MFnCamera(camera)
			upDir = fnCamera.upDirection(spc)
			rightDir = fnCamera.rightDirection(spc)

			# Determine the camera used in the current view
			#
			if fnCamera.isOrtho():
				if upDir.isEquivalent(OpenMaya.MVector.zNegAxis, kVectorEpsilon):
					self.__currWin = MoveContext.kTop
				elif rightDir.isEquivalent(OpenMaya.MVector.xAxis, kVectorEpsilon):
					self.__currWin = MoveContext.kFront
				else:
					self.__currWin = MoveContext.kSide
			else:
				self.__currWin = MoveContext.kPersp

			# Create an instance of the move tool command.
			#
			newCmd = self._newToolCommand()
			self.__cmd = kTrackingDictionary.get(OpenMayaMPx.asHashable(newCmd), None)
			self.__cmd.setVector(0.0, 0.0, 0.0)

	def doDrag(self, event):
		OpenMayaMPx.MPxSelectionContext.doDrag(self, event)

		# If we are not in selecting mode (i.e. an object has been selected)
		# then do the translation.
		#

		if not self._isSelecting():
			argX = OpenMaya.MScriptUtil()
			argX.createFromInt(0)
			argXPtr = argX.asShortPtr()
			argY = OpenMaya.MScriptUtil()
			argY.createFromInt(0)
			argYPtr = argY.asShortPtr()
			event.getPosition(argXPtr, argYPtr)
			self.__endPos_x = OpenMaya.MScriptUtil(argXPtr).asShort()
			self.__endPos_y = OpenMaya.MScriptUtil(argYPtr).asShort()

			startW = OpenMaya.MPoint()
			endW = OpenMaya.MPoint()
			vec = OpenMaya.MVector()
			self.__view.viewToWorld(self.__startPos_x, self.__startPos_y, startW, vec)
			self.__view.viewToWorld(self.__endPos_x, self.__endPos_y, endW, vec)
			downButton = event.mouseButton()

			# We reset the the move vector each time a drag event occurs
			# and then recalculate it based on the start position.
			#
			self.__cmd.undoIt()
			if self.__currWin == MoveContext.kTop:
				if downButton == OpenMayaUI.MEvent.kMiddleMouse:
					self.__cmd.setVector(endW.x - startW.x, 0.0, 0.0)
				else:
					self.__cmd.setVector(endW.x - startW.x, 0.0, endW.z - startW.z)

			elif self.__currWin == MoveContext.kFront:
				if downButton == OpenMayaUI.MEvent.kMiddleMouse:

					self.__cmd.setVector(endW.x - startW.x, 0.0, 0.0)

				else:

					self.__cmd.setVector(endW.x - startW.x, endW.y - startW.y, 0.0)

			elif self.__currWin == MoveContext.kSide:
				if downButton == OpenMayaUI.MEvent.kMiddleMouse:
					self.__cmd.setVector(0.0, 0.0, endW.z - startW.z)
				else:
					self.__cmd.setVector(0.0, endW.y - startW.y, endW.z - startW.z)

			self.__cmd.redoIt()
			self.__view.refresh(True)

	def doRelease(self, event):
		OpenMayaMPx.MPxSelectionContext.doRelease(self, event)
		if not self._isSelecting():
			argX = OpenMaya.MScriptUtil()
			argX.createFromInt(0)
			argXPtr = argX.asShortPtr()
			argY = OpenMaya.MScriptUtil()
			argY.createFromInt(0)
			argYPtr = argY.asShortPtr()
			event.getPosition(argXPtr, argYPtr)
			self.__endPos_x = OpenMaya.MScriptUtil(argXPtr).asShort()
			self.__endPos_y = OpenMaya.MScriptUtil(argYPtr).asShort()

			# Delete the move command if we have moved less then 2 pixels
			# otherwise call finalize to set up the journal and add the
			# command to the undo queue.

			#
			if (math.fabs(self.__startPos_x - self.__endPos_x) < 2 and 
					math.fabs(self.__startPos_y - self.__endPos_y) < 2):
				self.__cmd = None
				self.__view.refresh(True)
			else:
				self.__cmd.finalize()
				self.__view.refresh(True)

	def doEnterRegion(self, event):
		"""
		Print the tool description in the help line.
		"""
		self._setHelpString("drag to move selected object")


#############################################################################


class MoveContextCommand(OpenMayaMPx.MPxContextCommand):
	def __init__(self):
		OpenMayaMPx.MPxContextCommand.__init__(self)

	def makeObj(self):
		return OpenMayaMPx.asMPxPtr(MoveContext())

def cmdCreator():
	return OpenMayaMPx.asMPxPtr(MoveToolCmd())

def ctxCmdCreator():
	return OpenMayaMPx.asMPxPtr(MoveContextCommand())

def syntaxCreator():
	syntax = OpenMaya.MSyntax()
	syntax.addArg(OpenMaya.MSyntax.kDouble)
	syntax.addArg(OpenMaya.MSyntax.kDouble)
	syntax.addArg(OpenMaya.MSyntax.kDouble)
	return syntax

# Initialize the script plug-in

def initializePlugin(mobject):
	mplugin = OpenMayaMPx.MFnPlugin(mobject, "Autodesk", "1.0", "Any")
	try:
		mplugin.registerContextCommand(kPluginCtxName, ctxCmdCreator, kPluginCmdName, cmdCreator, syntaxCreator)
	except:
		sys.stderr.write("Failed to register context command: %s\n" % kPluginCtxName)
		raise

# Uninitialize the script plug-in
def uninitializePlugin(mobject):
	mplugin = OpenMayaMPx.MFnPlugin(mobject)
	try:
		mplugin.deregisterContextCommand(kPluginCtxName, kPluginCmdName)
	except:
		sys.stderr.write("Failed to deregister context command: %s\n" % kPluginCtxName)
		raise

